package filesystem

import (
	"fmt"
	"path/filepath"
	"strings"
)

var PrefixDefault string = "--"

type File struct {
	P       string
	Name    string
	next    map[string]*File
	parent  *File
	is_root bool
	is_file bool
	md5     string
}

func (n *File) MD5() string {
	if !n.is_file {
		return ""
	}

	if len(n.md5) == 0 {
		n.RefreshMD5()
	}

	return n.md5
}

func (n *File) Dir() string {
	if n.is_file {
		return n.parent.Path()
	}

	return n.Path()
}

func (n *File) HasChild() bool {
	return len(n.next) > 0
}

func (n *File) Parent() *File {
	return n.parent
}

func (n *File) Root() *File {
	root := n
	for !root.is_root {
		root = root.parent
	}
	return root
}

func (n *File) RefreshMD5() {
	if !n.is_file {
		return
	}

	n.md5 = CaculateMD5(filepath.Join(DataDir, n.Path()))
}

func (n *File) dump(prefix string) string {
	var res string
	res += fmt.Sprintf("%v%v\n", prefix, n.Name)
	for _, v := range n.next {
		res += v.dump(prefix + PrefixDefault)
	}
	return res
}

func (n *File) Dump(prefix string) string {
	return n.dump(prefix)
}

func (n *File) IsDir() bool {
	return !n.is_file
}

func (n *File) Path() string {
	if n.is_root {
		return ""
	}
	if n.parent.is_root {
		return n.Name
	}

	if len(n.P) > 0 {
		return n.P
	}
	return n.parent.Path() + "/" + n.Name
}

func (n *File) getFiles(files *Files) {
	if len(n.next) == 0 {
		// 到达叶子节点, 存储文件
		*files = append(*files, n)
		return
	}

	for _, nn := range n.next {
		nn.getFiles(files)
	}
}

func (n *File) GetFiles() Files {
	files := make(Files, 0)
	n.getFiles(&files)
	return files
}

func (p *File) AddFile(path string, is_file bool, md5 string) {
	node := p
	for len(path) > 0 {
		file, sub := split(path)
		var n *File
		var ok bool
		if n, ok = node.next[file]; !ok {
			n = &File{
				Name:    file,
				next:    make(map[string]*File),
				parent:  node,
				is_root: false,
			}
			n.P = n.Path()
			node.next[file] = n
		}
		node = n
		path = sub
	}

	if node != p {
		node.is_file = is_file
		node.md5 = md5
	}
}

func (p *File) Find(path string) *File {
	node := p
	for len(path) > 0 {
		file, sub := split(path)
		var n *File
		var ok bool
		if n, ok = node.next[file]; !ok {
			return nil
		}
		node = n
		path = sub
	}

	if node.is_root {
		return nil
	}
	return node
}

func (p *File) Del(path string) {
	node := p.Find(path)
	if node != nil {
		delete(node.parent.next, node.Name)
	}
}

func (p *File) IsExist(path string) bool {
	node := p
	for len(path) > 0 {
		file, sub := split(path)
		var n *File
		var ok bool
		if n, ok = node.next[file]; !ok {
			return false
		}
		node = n
		path = sub
	}
	return true
}

func (p *File) IsBelongTo(path string) bool {
	router := make(Files, 0)
	file := p
	for !file.is_root {
		router = append(router, file)
		file = file.parent
	}
	new_path := NewPath()
	new_path.Add(path)
	new_file := new_path.root
	for i := len(router) - 1; i >= 0; i-- {
		if !new_file.HasChild() {
			break
		}
		if nf, ok := new_file.next[router[i].Name]; ok {
			new_file = nf
		} else {
			break
		}
	}
	return !new_file.HasChild()
}

type Path struct {
	root *File
}

func split(path string) (string, string) {
	path = strings.ReplaceAll(path, "\\", "/")
	n := strings.Index(path, "/")
	if n < 0 {
		return path, ""
	}

	return path[0:n], path[n+1:]
}

func (p *Path) Add(file string) {
	p.root.AddFile(file, true, "")
}

func (p *Path) AddFile(path string, is_file bool) {
	p.root.AddFile(path, is_file, "")
}

func (p *Path) AddFile2(path string, is_file bool, md5 string) {
	p.root.AddFile(path, is_file, md5)
}

func (p *Path) Find(path string) *File {
	return p.root.Find(path)
}

func (p *Path) Del(path string) {
	p.root.Del(path)
}

func (p *Path) IsExist(path string) bool {
	return p.root.IsExist(path)
}

func (p *Path) Dump() string {
	return p.root.dump("")
}

func (p *Path) GetFiles() Files {
	return p.root.GetFiles()
}

func (p *Path) GetRootDir() Files {
	files := make(Files, 0)
	for _, n := range p.root.next {
		files = append(files, n)
	}
	return files
}

func NewPath() *Path {
	return &Path{
		root: &File{
			Name:    "",
			next:    make(map[string]*File),
			parent:  nil,
			is_root: true,
		},
	}
}

type Files []*File

func (f *Files) RefreshMD5() {
	for _, fi := range *f {
		fi.RefreshMD5()
	}
}

func (f *Files) GetAllDir() []string {
	uni := make(map[string]bool)
	for _, fi := range *f {
		if _, ok := uni[fi.Dir()]; !ok {
			uni[fi.Dir()] = true
		}
	}
	rest := make([]string, 0)
	for k, _ := range uni {
		rest = append(rest, k)
	}
	return rest
}

func (f *Files) GetAllParentDir() []string {
	uni := make(map[string]bool)
	for _, fi := range *f {
		if _, ok := uni[fi.Parent().Dir()]; !ok {
			uni[fi.Parent().Dir()] = true
		}
	}
	rest := make([]string, 0)
	for k, _ := range uni {
		rest = append(rest, k)
	}
	return rest
}

func (f *Files) GetRootDir() []string {
	uni := make(map[string]bool)
	for _, fi := range *f {
		root := fi.Root()
		for k, _ := range root.next {
			if _, ok := uni[k]; !ok {
				uni[k] = true
			}
		}

	}
	rest := make([]string, 0)
	for k, _ := range uni {
		rest = append(rest, k)
	}
	return rest
}
